package de.schule.barnimgymnasium.javafx.broadcaster;

import java.io.File;
import java.net.InetAddress;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Hashtable;
import java.util.Map;
import java.util.prefs.Preferences;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Stage;
import de.schule.barnimgymnasium.javafx.broadcaster.MemberProfile.Gender;
import de.schule.barnimgymnasium.javafx.broadcaster.Network.IncomingObject;

public class ChatClient extends Application {

    /**
     * Die Informationen über den lokal angemeldeten Benutzer
     */
    MemberProfile meMyselfAndI = null;
    final Preferences prefs = Preferences.userRoot().node(this.getClass().getName());
    final String NICKNAME = "NICKNAME";
    final String FACE_IMAGE = "FACE_IMAGE";
    final String GENDER = "GENDER";
    /**
     * Eine Fläche mit all den Nachrichten, die durchzulesen sind
     */
    Network network;
    /**
     * Die Liste mit den anzuzeigenden Nachrichten
     */
    ObservableList<Node> messageDisplay;
    /**
     * Die Zuordnung von empfangenen Profilen zu den Absendern
     */
    Map<InetAddress, MemberProfile> profiles = new Hashtable<InetAddress, MemberProfile>();

    /**
     * Legt das Aussehen und das Verhalten der Haupt-Scene fest. Aufgabe der Haupt-Scene ist das Lesen und Schreiben der Nachrichten,
     * und das Starten und Anzeigen möglicherweise noch kommender Erweiterungen.
     * 
     * @return die Haupt-Scene
     */
    private Scene createMainScene() {
        final TextField toWrite = new TextField();
        toWrite.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(final ActionEvent event) {
                if (toWrite.getLength() > 0) {
                    final String text = toWrite.getText();
                    ChatClient.this.network.broadcastObject(text);
                    ChatClient.this.messageDisplay.add(ChatClient.this.createMessageDisplay(ChatClient.this.meMyselfAndI, text));
                    toWrite.clear();
                }
            }
        });
        final VBox toRead = new VBox();
        this.messageDisplay = toRead.getChildren();
        final ScrollPane scroll = new ScrollPane();
        scroll.setFitToHeight(true);
        scroll.setFitToWidth(true);
        scroll.setContent(toRead);
        VBox.setVgrow(scroll, Priority.ALWAYS);
        final VBox readAndWrite = new VBox();
        readAndWrite.getChildren().addAll(scroll, toWrite);
        final BorderPane pane = new BorderPane();
        pane.setCenter(readAndWrite);
        return new Scene(pane, 600, 400);
    }

    /**
     * Wandelt eine eingehende Nachricht in eine anzeigbare Node um
     * 
     * @param incoming
     *        die eingehende Nachricht
     * @return die anzeigbare Node
     */
    private Node createMessageDisplay(final IncomingObject incoming) {
        if (incoming == null) {
            return new Label("Leere Nachricht");
        }
        final InetAddress sender = incoming.getAddress();
        final MemberProfile member = this.profiles.get(sender);
        final Object obj = incoming.getObject();
        return this.createMessageDisplay(member, obj);
    }

    private Node createMessageDisplay(final MemberProfile member, final Object obj) {
        final Button nickname = new Button();
        final Text timestamp = new Text();
        final Node message;
        final Font systemFont = nickname.getFont();
        final Font bold = Font.font(systemFont.getName(), FontWeight.BOLD, systemFont.getSize());
        final Calendar cal = Calendar.getInstance();
        final SimpleDateFormat formater = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
        timestamp.setText("[" + formater.format(cal.getTime()) + "]");
        if (member == null) {
            nickname.setText("???");
            // TODO: requestProfile();
        } else if (member.getNickname() == null) {
            nickname.setText("anonymous:");
        } else {
            nickname.setText(member.getNickname() + ":");
            nickname.setGraphic(new ImageView(member.getFaceAsImage()));
        }
        nickname.setFont(bold);
        if (obj instanceof String) {
            final Text txt = new Text();
            txt.setText((String)obj);
            message = txt;
        } else {
            final Text txt = new Text();
            txt.setText("Unbekanntes Nachrichtenformat");
            message = txt;
        }
        final HBox ret = new HBox();
        ret.setSpacing(5.0);
        ret.setAlignment(Pos.CENTER_LEFT);
        VBox.setMargin(ret, new Insets(5.0, 5.0, 5.0, 5.0));
        ret.getChildren().addAll(timestamp, nickname, message);
        return ret;
    }

    private Network createNetwork() throws SocketException {
        final Network ret = new Network();
        ret.setOnSucceeded(new EventHandler<WorkerStateEvent>() {

            @Override
            public void handle(final WorkerStateEvent ev) {
                if (ev.getSource() instanceof Network) {
                    final Network n = (Network)ev.getSource();
                    Object obj = n.getValue();
                    if (obj instanceof IncomingObject) {
                        final IncomingObject incoming = (IncomingObject)obj;
                        obj = incoming.getObject();
                        if (obj instanceof MemberProfile) {
                            final MemberProfile member = (MemberProfile)obj;
                            if (member.getNickname() == null) {
                                // request for my profile, broadcast
                                ChatClient.this.network.broadcastObject(ChatClient.this.meMyselfAndI);
                            } else {
                                ChatClient.this.profiles.put(incoming.getAddress(), (MemberProfile)incoming.getObject());
                            }
                        } else {
                            if (!ChatClient.this.network.getLocalAddresses().contains(incoming.getAddress())) {

                                // Die Nachricht zur Liste der anzeigbaren
                                // Nachrichten hinzufügen
                                ChatClient.this.messageDisplay.add(ChatClient.this.createMessageDisplay(incoming));
                                // Dafür sorgen, dass nicht unendlich viele
                                // Nachrichten aufbewahrt werden
                                while (ChatClient.this.messageDisplay.size() > 1000) {
                                    // die älteste(n) Nachricht löschen, wer weiß,
                                    // ob die 1000 nicht mal variabel wird
                                    ChatClient.this.messageDisplay.remove(0);
                                }
                            } else {
                                // Ignoriere die eigenen Nachrichten
                            }
                        }
                    }
                    n.restart();
                }
            }
        });
        ret.start();
        return ret;
    }

    private Scene createLoginScene() {

        final Button faceButton = new Button();

        final Image face = this.meMyselfAndI.getFaceAsImage();
        if (face == null) {
            faceButton.setText("Foto?");
        } else {
            faceButton.setGraphic(new ImageView(face));
        }
        faceButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(final ActionEvent arg0) {
                final FileChooser fc = new FileChooser();
                fc.getExtensionFilters().add(new ExtensionFilter("PNG", "*.png"));
                fc.getExtensionFilters().add(new ExtensionFilter("JPEG", "*.jpg", "*.jpeg"));
                final File choosen = fc.showOpenDialog(null);
                if (choosen != null) {
                    final String imagefile = choosen.getAbsolutePath();
                    ChatClient.this.meMyselfAndI.setFace(choosen);
                    final Image face = ChatClient.this.meMyselfAndI.getFaceAsImage();
                    faceButton.setGraphic(new ImageView(face));
                    faceButton.setText(null);
                    ChatClient.this.prefs.put(ChatClient.this.FACE_IMAGE, imagefile);
                }
            }
        });
        final TextField text = new TextField();
        text.setPromptText("Gib Deinen Nicknamen ein");
        text.setText(this.meMyselfAndI.getNickname());
        final ChoiceBox<Gender> geschlecht = new ChoiceBox<Gender>();
        text.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(final ActionEvent event) {
                if (text.getText().length() > 0) {
                    final String nickname = text.getText();
                    ChatClient.this.meMyselfAndI.setNickname(nickname);
                    ChatClient.this.prefs.put(ChatClient.this.NICKNAME, nickname);
                    final Scene mainScene = ChatClient.this.createMainScene();
                    ChatClient.this.network.broadcastObject(ChatClient.this.meMyselfAndI);
                    ((Stage)text.getScene().getWindow()).setScene(mainScene);
                }
            }
        });
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                text.requestFocus();
            }
        });
        geschlecht.getItems().add(Gender.MALE); // new ImageView(new
                                                // Image("file://männlich-32.png")));
        geschlecht.getItems().add(Gender.FEMALE); // new ImageView(new
                                                  // Image("file://weiblich-32.png")));
        // geschlecht.getItems().addAll(new Separator()); // new ImageView(new
        // Image("file://intersex-32.png")));
        // geschlecht.getItems().addAll(new Label("file://intersex-32.png")); //
        // new ImageView(new Image("file://intersex-32.png")));
        geschlecht.getItems().add(Gender.OTHER); // new ImageView(new
                                                 // Image("file://intersex-32.png")));
        geschlecht.getSelectionModel().select(this.meMyselfAndI.getGender());
        geschlecht.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(final ObservableValue<? extends Number> arg0, final Number oldValue, final Number newValue) {
                final Gender g = geschlecht.getItems().get(newValue.intValue());
                ChatClient.this.meMyselfAndI.setGender(g);
                ChatClient.this.prefs.put(ChatClient.this.GENDER, g.name());
            }
        });
        final HBox loginInfos = new HBox();
        loginInfos.setAlignment(Pos.CENTER);
        loginInfos.setSpacing(10.0);
        loginInfos.getChildren().addAll(faceButton, text, geschlecht);

        final StackPane pane = new StackPane();
        StackPane.setMargin(loginInfos, new Insets(30.0));
        pane.getChildren().add(loginInfos);
        return new Scene(pane);
    }

    private Scene createKeineNetzwerkverbindung() {
        final Label label = new Label();
        label.setText("Keine Netzwerkverbindung");
        final StackPane pane = new StackPane();
        pane.getChildren().add(label);
        return new Scene(pane);
    }

    @Override
    public void start(final Stage primaryStage) {
        try {
            this.network = this.createNetwork();
            final String nickname = this.prefs.get(this.NICKNAME, "");
            File face = null;
            final String imagefile = this.prefs.get(this.FACE_IMAGE, null);
            if (imagefile != null && !imagefile.isEmpty()) {
                face = new File(imagefile);
            }
            final Gender g = Enum.valueOf(Gender.class, this.prefs.get(this.GENDER, "OTHER"));
            this.meMyselfAndI = new MemberProfile(nickname, face, g);
            final Scene startScene = this.createLoginScene();
            primaryStage.setScene(startScene);
        } catch (final SocketException e) {
            primaryStage.setScene(this.createKeineNetzwerkverbindung());
        }
        primaryStage.setTitle("Skaip oder so...");
        primaryStage.show();
    }

    /**
     * @param args
     */
    public static void main(final String[] args) {
        Application.launch(args);
    }
}
